#include "colorBuffer.h"

ColorBuffer::ColorBuffer() {
}

ColorBuffer::ColorBuffer(PaintCursor *p, int w, int h) {
   shader_buffer = new glslKernel();
   shader_buffer->fragment_source("../paint/shader_color_buffer.frag");
   cout << "shader_buffer->install(true);" << endl;
   shader_buffer->install(true);

   pc = p;
   bw = w;
   bh = h;
   buffer = vector<Color>((w + 1) * (h + 1), Color(1.0, 1.0, 1.0, 1.0));
   isPainted = vector<bool>((w + 1) * (h + 1), false);
   tmpPainted = vector<bool>((w + 1) * (h + 1), false);
   havePrevious = false;
   cursor_format = 0.0;
   bdSetted = false;
   sMin = sMax = iPoint(0, 0, 0);
}

ColorBuffer::~ColorBuffer() {
}

void ColorBuffer::paint() {

   if (pc->isClicked()) {
      iPoint sp = pc->getScreenPosition();
      Color c = pc->getColor();

      if (cursor_format != pc->getFormat()) {
         cursor_format = pc->getFormat();
         vector<iPoint> pts = pc->getCursorPointsi();
         cursorvecs.clear();
         for (int i = 0; i < pts.size(); i++) {
            cursorvecs.push_back(pts[i] - sp);
         }
      }

      paintCursor(sp, c);
      floodFill(sp, c);
      clearTemp();

      // linking points
      if (havePrevious) {
         vector <iPoint> l = bresenham(sp, previous);
         for (int i = 0; i < l.size(); i++) {
            paintCursor(l[i], c);
            floodFill(l[i], c);
            clearTemp();
         }
      } else {
         havePrevious = true;
      }
      previous = sp;
   }
}

void ColorBuffer::paintCursor(iPoint p, Color c) {
   iPoint prv = p + cursorvecs[cursorvecs.size() - 1];
   for (int k = 0; k < cursorvecs.size(); k++) {
      iPoint pt = p + cursorvecs[k];
      if (inModel(pt)) {
         buffer[pt[0] + bw * pt[1]] = c;
         isPainted[pt[0] + bw * pt[1]] = true;
         updateStrokeBounds(pt);
      }
      tmpPainted[pt[0] + bw * pt[1]] = true;
      tmpIdx.push_back(pt[0] + bw * pt[1]);
      vector <iPoint> l = bresenham(pt, prv);
      for (int j = 0; j < l.size(); j++) {
         if (inModel(l[j])) {
            buffer[l[j][0] + bw * l[j][1]] = c;
            isPainted[l[j][0] + bw * l[j][1]] = true;
            updateStrokeBounds(l[j]);
         }
         tmpPainted[l[j][0] + bw * l[j][1]] = true;
         tmpIdx.push_back(l[j][0] + bw * l[j][1]);
      }
      prv = pt;
   }
}

void ColorBuffer::floodFill(iPoint p, Color c) {
   // non recursive floodfill
   std::queue<iPoint> q;
   q.push(p);
   while (!q.empty()) {
      iPoint px = q.front();
      q.pop();
      if (px[0] + bw * px[1] < 0){
         cout << " floodFill error ->" << px;
      }
      if (tmpPainted[px[0] + bw * px[1]]) continue;
      tmpPainted[px[0] + bw * px[1]] = true;
      tmpIdx.push_back(px[0] + bw * px[1]);
      q.push(iPoint(px[0] + 1, px[1], px[2]));
      q.push(iPoint(px[0] - 1, px[1], px[2]));
      q.push(iPoint(px[0], px[1] + 1, px[2]));
      q.push(iPoint(px[0], px[1] - 1, px[2]));
      if (inModel(px)) {
         buffer[px[0] + bw * px[1]] = c;
         isPainted[px[0] + bw * px[1]] = true;
         updateStrokeBounds(px);
      }
   }
// recursive floodfill
//   if (tmpPainted[p[0] + bw * p[1]]) return;
//   cout << v << ": " << p << endl;
//   buffer[p[0] + bw * p[1]] = c;
//   isPainted[p[0] + bw * p[1]] = true;
//   tmpPainted[p[0] + bw * p[1]] = true;
//   floodFill(iPoint(p[0] + 1, p[1], p[2]), c);
//   floodFill(iPoint(p[0] - 1, p[1], p[2]), c);
//   floodFill(iPoint(p[0], p[1] + 1, p[2]), c);
//   floodFill(iPoint(p[0], p[1] - 1, p[2]), c);
}

void ColorBuffer::unpaint() {
   havePrevious = false;
}

void ColorBuffer::clearTemp() {
   // Cleaning temporary buffer efficiently
   for (int i = 0; i < tmpIdx.size(); i++) {
      tmpPainted[tmpIdx[i]] = false;
   }
   tmpIdx.clear();
}

void ColorBuffer::draw() {
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(0, bw + 1, 0, bh + 1, -4, 4);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   Vector color;
   Color n;
   Vector normal;

   Color ambient(1.0, 1.0, 1.0);
   Color specular(1.0, 1.0, 1.0);
   Color diffuse(1.0, 1.0, 1.0);

   Point ld = pc->lightVector();
   Vector lightDir(ld[0], ld[1], ld[2]);
   lightDir.normalize();

   Vector ambientLight = pc->ambientLight();
   Vector diffuseLight = pc->diffuseLight();
   Vector specularLight = pc->specularLight();
   float shininess = 125.0000;
   float NdotL = 0.0;
   float NdotHV  = 0.0;

   glBegin(GL_POINTS);
   for (int i = 0; i < bw; i++) {
      for (int j = 0; j < bh; j++) {
         if (isPainted[i + bw * j]) {
            color = Vector(buffer[i + bw * j][0], buffer[i + bw * j][1], buffer[i + bw * j][2]);
            n = pc->getNormal(i, j);
            normal = Vector(n[0], n[1], n[2]);
            diffuse = Color(color[0], color[1], color[2]);

            color[0] = ambient[0] * ambientLight[0];
            color[1] = ambient[1] * ambientLight[1];
            color[2] = ambient[2] * ambientLight[2];

            NdotL = max(normal * lightDir, 0.0);
            if (NdotL > 0.0) {
               color[0] += diffuse[0] * diffuseLight[0] * NdotL;
               color[1] += diffuse[1] * diffuseLight[1] * NdotL;
               color[2] += diffuse[2] * diffuseLight[2] * NdotL;

               NdotHV = max(normal * Vector(0.0, 0.0, 1.0), 0.0);

               color[0] += specular[0] * specularLight[0] * pow(NdotHV, shininess);
               color[1] += specular[1] * specularLight[1] * pow(NdotHV, shininess);
               color[2] += specular[2] * specularLight[2] * pow(NdotHV, shininess);
            }

            glColor4f(color[0], color[1], color[2], 1.0);
            glVertex2i(i, j);
         }
      }
   }
   glEnd();
}

void ColorBuffer::draw2() {

   glEnable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D, texPositions);
   glBegin(GL_QUADS);
     glTexCoord2f(0.0f, 0.0f); glVertex3f(0.0f, 0.0f,  0.0f);
     glTexCoord2f(1.0f, 0.0f); glVertex3f(sMax[0] - sMin[0], 0.0f,  0.0f);
     glTexCoord2f(1.0f, 1.0f); glVertex3f(sMax[0] - sMin[0], sMax[1] - sMin[1],  0.0f);
     glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, sMax[1] - sMin[1],  0.0f);
   glEnd();
   glDisable(GL_TEXTURE_2D);

   glColor3d(0.0, 0.0, 0.0);
   glBegin(GL_LINE_LOOP);
   glVertex2i(sMin[0], sMin[1]);
   glVertex2i(sMin[0], sMax[1]);
   glVertex2i(sMax[0], sMax[1]);
   glVertex2i(sMax[0], sMin[1]);
   glEnd();
}

GLuint ColorBuffer::textureTest() {
   cout << sMin << sMax;
   int   tw = sMax[0] - sMin[0],
         th = sMax[1] - sMin[1];

   vector<float> texImage;

   for (int j = sMin[1]; j < sMax[1]; j++) {
      for (int i = sMin[0]; i < sMax[0]; i++) {
         texImage.push_back(buffer[i + bw * j][0]);
         texImage.push_back(buffer[i + bw * j][1]);
         texImage.push_back(buffer[i + bw * j][2]);
         texImage.push_back(buffer[i + bw * j][3]);
      }
   }

   glDeleteTextures(1, &texPositions);

   glEnable(GL_TEXTURE_2D);
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
   glGenTextures(1, &texPositions);
   glBindTexture(GL_TEXTURE_2D, texPositions);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, GL_FLOAT, &texImage[0]);
   glDisable(GL_TEXTURE_2D);
   cout << "textura criada!! " << endl;

   GLfloat coordX, coordY;
   for (int j = 0; j < tw; j++) {
      for (int i = 0; i < th; i++) {
         GLfloat id = pc->getIdPoint(sMin[0] + i, sMin[1] + j);
         if (id != -1) {
            coordX = (float) i / tw;
            coordY = (float) j / th;
//            cout << coordX << ", " << coordY << ", " << id << endl;
            surfels->at((int) id).SetColor(Color(coordX, coordY, 0.0));
//            cout << buffer[i + bw * j][0] << ", " << buffer[i + bw * j][1] << ", " << buffer[i + bw * j][2] << endl;
//            cout << texImage[(i + tw * j) * 4] << ", " << texImage[(i + tw * j) * 4 + 1] << ", " << texImage[(i + tw * j) * 4 + 2] << endl;
//            surfels[(int) id].SetColor(Color(texImage[(i + tw * j) * 4], texImage[(i + tw * j) * 4 + 1], texImage[(i + tw * j) * 4 + 2]));
         }
      }
   }
   cout << "coordenadas de textura associadas!! " << endl;
   cout << texPositions << endl;
   return texPositions;
}

bool ColorBuffer::inModel(iPoint px) {
   Color n = pc->getNormal(px[0], px[1]);
   return(n[3] == 1);
}

void ColorBuffer::updateStrokeBounds(iPoint pt) {
   if (!bdSetted) {
      bdSetted = true;
      sMin = sMax = pt;
      return;
   }
   if (sMin[0] > pt[0]) sMin[0] = pt[0];
   if (sMin[1] > pt[1]) sMin[1] = pt[1];
   if (sMax[0] < pt[0]) sMax[0] = pt[0];
   if (sMax[1] < pt[1]) sMax[1] = pt[1];
}

vector<iPoint> ColorBuffer::bresenham(iPoint sr, iPoint tg) {
   vector<iPoint> l;

   int x1 = sr[0], y1 = sr[1], x2 = tg[0], y2 = tg[1];

   int t = 1;
   if (x2 < x1) {
      int aux;
      aux = x1;
      x1 = x2;
      x2 = aux;
      aux = y1;
      y1 = y2;
      y2 = aux;
   }
   if (y2 < y1) {
      y1 = -y1;
      y2 = -y2;
      t = -1;
   }
   bool w;
   int a, b, V;
   if (abs(y2 - y1) > abs(x2 - x1)) {
      w = false;
      a = y1 - y2;
      b = x2 - x1;
      V = a + b + b;
   } else {
      w = true;
      a = y2 - y1;
      b = x1 - x2;
      V = a + a + b;
   }
   int x = x1;
   int y = y1;

   if (w) {
      while (x < x2) {
         l.push_back(iPoint(x, t*y, 0));
         x = x + 1;
         if (V <= 0) {
            V = V + a + a;
         } else {
            V = V + a + b + a + b;
            y = y + 1;
         }
      }
   } else {
      while (y < y2) {
         l.push_back(iPoint(x, t*y, 0));
         y = y + 1;
         if (V <= 0) {
            V = V + b + b;
         } else {
            V = V + a + b + a + b;
            x = x + 1;
         }
      }
   }
   return l;
}
